/*
 * Copyright 2009 Max Kugland
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.splink.deepsplink.boot {
	import org.splink.deepsplink.authentication.IAuthenticator;
	import org.splink.deepsplink.config.ConfigData;
	import org.splink.deepsplink.config.PageData;
	import org.splink.deepsplink.deeplink.DeeplinkController;
	import org.splink.deepsplink.deeplink.IDeeplinkController;
	import org.splink.deepsplink.deeplink.IDeeplinkStrategy;
	import org.splink.deepsplink.injector.AuthenticationInjector;
	import org.splink.deepsplink.injector.IPageInjector;
	import org.splink.deepsplink.interceptor.IInterceptor;
	import org.splink.deepsplink.interceptor.InvalidPageInterceptor;
	import org.splink.deepsplink.interceptor.SecurityInterceptor;
	import org.splink.deepsplink.navigation.command.INavigationCommand;
	import org.splink.deepsplink.navigation.command.NavigationCommand;
	import org.splink.deepsplink.page.PageFactory;
	import org.splink.deepsplink.page.request.IRequestProcessor;
	import org.splink.deepsplink.page.request.RequestProcessor;
	import org.splink.deepsplink.processor.IProcessor;
	import org.splink.library.tree.Tree;

	import flash.display.DisplayObject;
	import flash.display.Sprite;

	/**
	 * <code>DeepsplinkFacade</code> wires deesplink in a standard way so you rarely ever have to write 
	 * boilerplate glue code unless you have very special needs. The facade is promoted to 
	 * the client via the <code>IDeepsplinkBootstrapStrategy</code>.
	 * 
	 * @see org.splink.deepsplink.IDeepsplinkBootstrapStrategy
	 * @author Max Kugland
	 */
	final internal class DeepsplinkFacade implements IDeepsplinkFacade {
		private var _config : ConfigData;
		// contains the PageData instance in it's data property which is connected to the node's id
		private var _publicTree : Tree; 
		// contains the IPageInternal reference in it's data property which is connected to the node's id		private var _privateTree : Tree; 
		private var _deeplinkCtrl : IDeeplinkController;
		private var _navigationCmd : INavigationCommand;
		private var _pageFactory : PageFactory;
		private var _display : Sprite;
		private var _authenticator : IAuthenticator;
		private var _securityInterceptor : SecurityInterceptor;
		private var _deeplinkStrategy : IDeeplinkStrategy;
		private var _started : Boolean = false;

		/**
		 * @param tree the underlying (tree) data structure on which the framework is based
		 * @param config a <code>ConfigData</code> instance which holds all the neccessary configuration
		 * information for deepsplink
		 * @param deeplinkStrategy a <code>IDeeplinkStrategy</code> implementation, usually this is 
		 * the default implementation <code>SwfAddressDeeplinkStrategy</code>
		 */
		public function DeepsplinkFacade(tree : Tree, config : ConfigData, deeplinkStrategy : IDeeplinkStrategy) {
			_display = new Sprite();
			_privateTree = new Tree(Tree.clone(tree.root));
			_publicTree = tree;
			_config = config;
			_deeplinkStrategy = deeplinkStrategy;
			
			setup();
			addInvalidPageInterceptor();
		}

		private function setup() : void {
			_pageFactory = new PageFactory(_publicTree, _privateTree, this);
			var requestProcessor : IRequestProcessor = new RequestProcessor(_privateTree, _display, _config, _pageFactory);
			_deeplinkCtrl = new DeeplinkController(_config, requestProcessor, _deeplinkStrategy);
			_navigationCmd = new NavigationCommand(_deeplinkCtrl);
		}

		private function addInvalidPageInterceptor() : void {
			if(_config.notfoundData && _config.notfoundData.redirect) {
				addInterceptor(new InvalidPageInterceptor(_navigationCmd, _privateTree, _config.notfoundData.redirect));
			} else {
				throw new Error("You must define 'notfound' in your configuration.");
			}
		}
		
		/**
		 * Starts the framework by loading the first page
		 */
		public function start() : void {
			if(_started)
				throw new Error("Deepsplink is already running.");
			
			if(_config.eagerPages) {
				_pageFactory.preinstantiatePages();
			}
			_deeplinkCtrl.start();
			_started = true;
		}

		/**
		 * @inheritDoc
		 */
		public function addInjector(injector : IPageInjector) : void {
			if(_started) throw new Error("Injectors must be added before the facade is started.");
			_pageFactory.addInjector(injector);
		}

		/**
		 * @inheritDoc
		 */
		public function addInterceptor(interceptor : IInterceptor) : void {
			_deeplinkCtrl.addInterceptor(interceptor);
		}

		/**
		 * @inheritDoc
		 */
		public function addProcessor(processor : IProcessor) : void {
			_deeplinkCtrl.addProcessor(processor);
		}

		/**
		 * @inheritDoc
		 */
		public function finalize() : void {
			_deeplinkCtrl.finalize();
		}

		/**
		 * @inheritDoc
		 */
		public function get tree() : Tree {
			return _publicTree;
		}

		/**
		 * @inheritDoc
		 */
		public function get config() : ConfigData {
			return _config.clone();
		}

		/**
		 * @inheritDoc
		 */
		public function get navigationCommand() : INavigationCommand {
			return _navigationCmd;
		}

		/**
		 * @inheritDoc
		 */
		public function get display() : DisplayObject {
			return _display;
		}

		/**
		 * @inheritDoc
		 */
		public function registerAuthenticator(authenticator : IAuthenticator) : void {
			_authenticator = authenticator;
			registerSecurityInterceptor();
			addInjector(new AuthenticationInjector(_authenticator, _securityInterceptor));
		}

		/**
		 * Registres the default <code>IInterceptor</code>s for protected pages and invalid page requests
		 */
		private function registerSecurityInterceptor() : void {
			if(_config.protectData && _authenticator) {
				createSecurityInterceptor();
				_deeplinkCtrl.addInterceptor(_securityInterceptor);
			} else {
				throw new Error("You must set an IAuthenticator to handle authentication for protected pages.");
			}
		}

		private function createSecurityInterceptor() : void {
			_securityInterceptor = new SecurityInterceptor(_navigationCmd, _publicTree, _config.protectData.redirect);
			_securityInterceptor.setAuthenticationManager(_authenticator);
			
			for each (var pageData : PageData in _config.protectData.pageDataArray) {
				_securityInterceptor.addProtectedId(pageData.id);
			}
		}
	}
}
